/**
 * Matter Accessory Cache
 *
 * Handles persistence of Matter accessories across Homebridge restarts.
 * Similar to HAP's cached accessories, but designed for Matter's simpler API.
 */

import type { MatterAccessory } from './matterTypes.js'

import * as path from 'node:path'

import fs from 'fs-extra'

import { Logger } from '../logger.js'

const log = Logger.withPrefix('Matter')

/**
 * Serializable Matter accessory (excludes functions and runtime state)
 */
export interface SerializedMatterAccessory {
  uuid: string
  displayName: string
  deviceType: {
    name?: string
    code?: number
  }
  serialNumber: string
  manufacturer: string
  model: string
  firmwareRevision?: string
  hardwareRevision?: string
  softwareVersion?: string
  context?: Record<string, any>
  clusters: {
    [clusterName: string]: {
      [attributeName: string]: any
    }
  }
}

/**
 * Matter Accessory Cache Manager
 */
export class MatterAccessoryCache {
  private readonly cacheFilePath: string
  private cachedAccessories: Map<string, SerializedMatterAccessory> = new Map()
  private cacheLoaded = false
  private saveInProgress: Promise<void> | null = null

  constructor(storagePath: string, bridgeId: string) {
    this.cacheFilePath = path.join(storagePath, bridgeId, 'accessories.json')
  }

  /**
   * Load cached accessories from disk
   */
  async load(): Promise<Map<string, SerializedMatterAccessory>> {
    if (this.cacheLoaded) {
      return this.cachedAccessories
    }

    try {
      // Ensure cache file exists
      if (!await fs.pathExists(this.cacheFilePath)) {
        log.info('No cached Matter accessories found (first run)')
        this.cacheLoaded = true
        return this.cachedAccessories
      }

      // Read and parse cache file
      const cacheData = await fs.readJson(this.cacheFilePath)

      if (!Array.isArray(cacheData)) {
        log.warn('Invalid Matter accessory cache format, deleting corrupted file')
        await fs.remove(this.cacheFilePath)
        this.cacheLoaded = true
        return this.cachedAccessories
      }

      // Load accessories into map
      for (const serialized of cacheData) {
        if (serialized.uuid) {
          this.cachedAccessories.set(serialized.uuid, serialized)
        }
      }

      log.info(`Loaded ${this.cachedAccessories.size} cached Matter accessories`)
      this.cacheLoaded = true

      return this.cachedAccessories
    } catch (error: any) {
      // If JSON parsing failed (corrupted file), delete it and start fresh
      log.error(`Failed to load Matter accessory cache: ${this.cacheFilePath}: ${error.message}`)
      log.warn('Deleting corrupted cache file and starting fresh')
      try {
        await fs.remove(this.cacheFilePath)
      } catch (removeError) {
        log.debug('Could not delete corrupted cache file:', removeError)
      }
      this.cacheLoaded = true
      return this.cachedAccessories
    }
  }

  /**
   * Save accessories to cache (serialized to prevent concurrent write conflicts)
   */
  async save(accessories: Map<string, MatterAccessory>): Promise<void> {
    // Wait for any in-progress save to complete
    if (this.saveInProgress) {
      await this.saveInProgress
    }

    // Start new save operation
    this.saveInProgress = this.performSave(accessories)

    try {
      await this.saveInProgress
    } finally {
      this.saveInProgress = null
    }
  }

  /**
   * Internal save implementation
   */
  private async performSave(accessories: Map<string, MatterAccessory>): Promise<void> {
    const tempFilePath = `${this.cacheFilePath}.tmp`

    try {
      // Serialize accessories (strip out functions)
      const serialized: SerializedMatterAccessory[] = []

      for (const accessory of accessories.values()) {
        serialized.push(this.serializeAccessory(accessory))
      }

      // Ensure directory exists
      const directory = path.dirname(this.cacheFilePath)
      await fs.ensureDir(directory)

      // Write to temporary file first (atomic write pattern to prevent corruption)
      await fs.writeJson(tempFilePath, serialized, { spaces: 2 })

      // Atomically rename temp file to final location
      // Use rename for true atomic operation on the same filesystem
      await fs.rename(tempFilePath, this.cacheFilePath)

      log.debug(`Saved ${serialized.length} Matter accessories to cache`)
    } catch (error: any) {
      log.error(`Failed to save Matter accessory cache: ${error.message}`)

      // Clean up temp file if it exists
      try {
        if (await fs.pathExists(tempFilePath)) {
          await fs.remove(tempFilePath)
        }
      } catch (cleanupError) {
        log.debug('Could not clean up temporary cache file:', cleanupError)
      }
    }
  }

  /**
   * Get cached accessory by UUID
   */
  getCached(uuid: string): SerializedMatterAccessory | undefined {
    return this.cachedAccessories.get(uuid)
  }

  /**
   * Check if an accessory is cached
   */
  hasCached(uuid: string): boolean {
    return this.cachedAccessories.has(uuid)
  }

  /**
   * Remove an accessory from cache
   */
  removeCached(uuid: string): void {
    this.cachedAccessories.delete(uuid)
  }

  /**
   * Get all cached accessories
   */
  getAllCached(): Map<string, SerializedMatterAccessory> {
    return new Map(this.cachedAccessories)
  }

  /**
   * Serialize a Matter accessory for storage
   */
  private serializeAccessory(accessory: MatterAccessory): SerializedMatterAccessory {
    // Extract device type information
    const deviceType: any = accessory.deviceType
    const deviceTypeInfo = {
      name: deviceType?.name,
      code: deviceType?.code,
    }

    return {
      uuid: accessory.uuid,
      displayName: accessory.displayName,
      deviceType: deviceTypeInfo,
      serialNumber: accessory.serialNumber,
      manufacturer: accessory.manufacturer,
      model: accessory.model,
      firmwareRevision: accessory.firmwareRevision,
      hardwareRevision: accessory.hardwareRevision,
      softwareVersion: accessory.softwareVersion,
      context: accessory.context,
      clusters: structuredClone(accessory.clusters),
    }
  }
}
